/*
 * Copyright (c) 2012 Johann Hanne
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

#include <stdlib.h>
#include <stdint.h>
#include <limits.h>

#include <putchar.h> /* Must be provided externally (i.e. not part of clib) */

/* An invalid character is indicated by a result of 127 */
#define ch_to_d(ch) \
      ((ch) <  '0') ? (127) : \
    ( ((ch) <= '9') ? ((ch) - '0') : \
    ( ((ch) <  'A') ? (127) : \
    ( ((ch) <= 'Z') ? ((ch) - ('A' - 10)) : \
    ( ((ch) <  'a') ? (127) : \
    ( ((ch) <= 'z') ? ((ch) - ('a' - 10)) : \
      (127) \
    )))))

/*
 * FIXME: errno is not set
 */
long int strtol(const char *nptr, char **endptr, int base) {
    const char *p = nptr;
    int8_t neg;
    int8_t d;
    long int result;
    int errflag = 0;
    long int limitbeforemult;

    while (*p == ' ' || (*p >= 0x09 && *p <= 0x0d)) {
        p++;
    }

    if (*p == '-') {
        p++;
        neg = -1;
    } else {
        if (*p == '+') {
            p++;
        }
        neg = 1;
    }

    if (base == 0) {
        if (*p == '0') {
            if (*(p + 1) == 'x') {
                base = 16;
                p += 2;
            } else {
                base = 8;
                p += 1;
            }
        } else {
            base = 10;
        }
    } else if (base == 16) {
        if (*p == '0' && *(p + 1) == 'x') {
            p += 2;
        }
    } else if (base == 1 || base > 35) {
        /* FIXME errno = EINVAL; */
        return 0;
    }

    d = ch_to_d(*p);
    if (d >= base) {
        /* No valid digits - we must use the *original* nptr here! */
        if (endptr != NULL) {
            *endptr = (char *)nptr;
        }
        return 0;
    }
    result = neg * d;
    p++;

    if (neg == -1) {

        limitbeforemult = LONG_MIN / base;

        while (*p != '\0') {
            d = ch_to_d(*p);

            if (d >= base) {
                if (endptr != NULL) {
                    *endptr = (char *)p;
                }
                break;
            }

            p++;

            if (errflag != 0) {
                continue;
            }

            if (result < limitbeforemult) {
                /* Multiplication would underflow */
                errflag = 1;
                continue;
            }

            result *= base;

            if (-d < LONG_MIN - result) {
                /* Subtraction would underflow */
                errflag = 1;
                continue;
            }

            result -= d;
        }

        if (errflag) {
            /* FIXME errno = ERANGE; */
            result = LONG_MIN;
        }

    } else {

        limitbeforemult = LONG_MAX / base;

        while (*p != '\0') {
            d = ch_to_d(*p);

            if (d >= base) {
                if (endptr != NULL) {
                    *endptr = (char *)p;
                }
                break;
            }

            p++;

            if (errflag != 0) {
                continue;
            }

            if (result > limitbeforemult) {
                /* Multiplication would overflow */
                errflag = 1;
                continue;
            }

            result *= base;

            if (d > LONG_MAX - result) {
                /* Addition would overflow */
                errflag = 1;
                continue;
            }

            result += d;
        }

        if (errflag) {
            /* FIXME errno = ERANGE; */
            result = LONG_MAX;
        }

    }

    if (endptr != NULL) {
        *endptr = (char *)p;
    }

    return result;
}

void abort(void) {
    putchar('A');
    putchar('B');
    putchar('O');
    putchar('R');
    putchar('T');
    putchar('!');
    putchar('\n');
    for (;;)
        ;
}
